/**
 *
 * © Bahadir Zeren 2011
 * bzeren@gmail.com
 * No warranties, express or implied, are made for this program.
 *
 */

package org.baatar.opt.genetic;

import org.baatar.opt.genetic.chromosome.IChromosomeFactory;
import org.baatar.opt.genetic.chromosome.IChromosome;
import org.baatar.opt.genetic.selection.ISelection;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import org.baatar.opt.genetic.crossover.ICrossoverOperator;
import org.baatar.opt.genetic.mutation.IMutator;
import org.baatar.thread.ThreadManager;
import org.baatar.thread.ThreadStatus;

/**
 * The Main genetic optimizer class.
 * 
 * Runs all functions according to chosen parameters and implementation classes.
 *
 */
public class GeneticOptimizer<T> extends ThreadManager {

    private int optimizerIndex = 0;

    private Comparator<IChromosome<T>> comparator = null;

    private IChromosomeFactory<T> chromosomeFactory = null;
    private ISelection<T> selector = null;
    private ICrossoverOperator<T> crossoverOperator = null;
    private IMutator<T> mutator = null;

    private int maxNumOfIterations = 500;
    private int maxNumOfIterationsWOProgress = 200;
    private int populationSize = 100;
    private int numOfChildren = 20;
    private int numOfEliteChromosomes = 4;
    private boolean allowDublicateChromosomes = false;

    private IChromosome<T> best = null;

    private IChromosome<T> population[] = null;
    private IChromosome<T> children[] = null;

    private double mutationRate = 0.01;
    private double greedRate = 0.01;

    private GeneticIterationListener<T> geneticIterationListener = null;

    /**
     * {@inheritDoc}
     */
    @Override
    protected void statusIsBeingChanged(ThreadStatus oldThreadStatus, ThreadStatus newThreadStatus) {
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void statusChanged(ThreadStatus oldThreadStatus, ThreadStatus newThreadStatus) {
    }

    private void iterate(int iteration,
                            int numOfIterationsPassedWOProgress) {
        if (geneticIterationListener != null)
            geneticIterationListener.onIterate(iteration,
                                                numOfIterationsPassedWOProgress,
                                                maxNumOfIterations);
    }

    private void progress(int iteration, String info) {
        if (geneticIterationListener != null)
            geneticIterationListener.onProgress(this, iteration, info);
    }

    private boolean initializePopulation() {
        int i = 0;

        if (chromosomeFactory == null)
            return false;

        while (i < populationSize) {
            IChromosome<T> chromosome = chromosomeFactory.createChromosome();
            chromosome.initializeChromosome();

            boolean addable = true;

            if (!allowDublicateChromosomes) {
                for (int j = i - 1; j >= 0; j--)
                    if (chromosome.isEqual(population[j]))
                        addable = false;
            }

            if (addable) {
                population[i] = chromosome;
                i++;
            }
        }
        return true;
    }

    private void orderPopulation() {
        for (int i = 0; i < populationSize - 1; i++) {
            for (int j = populationSize - 1; j > i; j--) {
                IChromosome<T> chromosome = population[i];
                IChromosome<T> iChromosome = population[j];

                if (comparator.compare(chromosome, iChromosome) < 0) {
                    population[i] = iChromosome;
                    population[j] = chromosome;
                }
            }
        }
    }

    private void generateAndMutateChildren(int iteration, int numOfIterationsWOProgress) throws CloneNotSupportedException,
                                                                                    InterruptedException {
        int i = 0;
        while (i < numOfChildren) {

            IChromosome<T> mother = selector.selectChromosomeForReproduction(population, comparator);
            IChromosome<T> father = selector.selectChromosomeForReproduction(population, comparator);

            while (mother == father) {
                father = selector.selectChromosomeForReproduction(population, comparator);
            }

            List<IChromosome<T>> childrenList = crossoverOperator.crossover(mother,
                                                                              father,
                                                                                population[populationSize - 1].getFitness());

            for (int j = 0; j < childrenList.size(); j++) {

                IChromosome<T> child = childrenList.get(j);

                children[i] = child;

                i++;

                if (mutationRate > 0.0)
                    mutator.mutate(child, mutationRate);
                child.calculateFitness();
                if (greedRate > 0.0)
                    mutator.mutateGreedy(child, greedRate, comparator);

                if (i == children.length)
                    break;
            }

        }
    }

    private void replacePopulation() {

        ArrayList<IChromosome<T>> ol = new ArrayList<IChromosome<T>>();

        for (int i = 0; i < numOfChildren; i++) {
            IChromosome<T> child = children[i];

            boolean canBeAdded = true;

            if (!allowDublicateChromosomes) {
                for (int j = 0; j < populationSize; j++) {
                    if (child.isEqual(population[j])) {
                        canBeAdded = false;
                        break;
                    }
                }

                for (int j = 0; j < ol.size(); j++) {
                    if (child.isEqual(ol.get(j))) {
                        canBeAdded = false;
                        break;
                    }
                }
            }

            if (canBeAdded) {
                ol.add(child);
            }
        }

        for (int i = 0; i < populationSize; i++) {
            ol.add(population[i]);
        }

        /*
         * Order population anc children chromosome objects list ol.
         * 
         */
        int olSize = ol.size();
        for (int i = 0; i < olSize - 1; i++) {
            for (int j = olSize - 1; j > i; j--) {
                IChromosome<T> chromosome = ol.get(i);
                IChromosome<T> iChromosome = ol.get(j);

                if (comparator.compare(chromosome, iChromosome) < 0) {
                    ol.set(i, iChromosome);
                    ol.set(j, chromosome);
                }
            }
        }

        /*
         * Keep first chromosomes as elite ones.
         * 
         */
        for (int i = 0; i < numOfEliteChromosomes; i++) {
            population[i] = ol.get(0);
            ol.remove(0);
        }

        for (int i = numOfEliteChromosomes; i < populationSize; i++) {
            int selectedChromosomeIndex = selector.selectChromosomeIndexForSurvival(ol, comparator);
            population[i] = ol.get(selectedChromosomeIndex);
            ol.remove(selectedChromosomeIndex);
        }

        ol.clear();

    }

    /**
     * Gives the best individual in the current population.
     * 
     * @return IChromosome the fittest individual in the current population.
     */
    private IChromosome<T> getFittestIndividual() {
        return population[0];
    }

    /**
     * Gives the best individual of the genetic optimization process.
     * 
     * @return IChromosome the fittest individual of the genetic optimization 
     * process.
     */
    public IChromosome<T> getBest() {
        return best;
    }

    @Override
    protected void goAhead() throws Exception {

        population = new IChromosome[populationSize];
        children = new IChromosome[numOfChildren];

        int numOfIterationsWOProgress = 0;

        try {
            if (initializePopulation()) {

                orderPopulation();

                best = getFittestIndividual();

                progress(0, "Optimization has been started...");
                progress(0, best.toString());
                iterate(0, 0);

//for (int j = 0; j < populationSize; j++)
//System.out.println("0-" + population[j]);
//System.out.println();

                for (int i = 1; i <= maxNumOfIterations && (!processCancelled()); i++) {

                    generateAndMutateChildren(i, numOfIterationsWOProgress);

                    replacePopulation();

//for (int j = 0; j < population.length; j++) {
//    for (int k = j + 1; k < population.length; k++) {
//if (population[j].equals(population[k]))
//System.out.println("Problem Dublicate Chromosome");
//    }
//}

                    IChromosome<T> ch = getFittestIndividual();

                    if (comparator.compare(best, ch) < 0) {
                        best = (IChromosome<T>) ch.clone();
                        numOfIterationsWOProgress = 0;
                        progress(i, best.toString());
                    } else
                        numOfIterationsWOProgress++;

                    iterate(i, numOfIterationsWOProgress);

                    if (numOfIterationsWOProgress >= maxNumOfIterationsWOProgress)
                        break;

//for (int j = 0; j < populationSize; j++)
//System.out.println(i + "-" + population[j]);
//System.out.println();

                }
                progress(maxNumOfIterations, "Optimization completed..");
                iterate(maxNumOfIterations, maxNumOfIterationsWOProgress);
            }
        } catch (Exception ex) {
            progress(maxNumOfIterations, ex.getMessage());
            iterate(maxNumOfIterations, maxNumOfIterations);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean initializeManager() {
        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void finalizeManager() {
    }

    /**
     * @return the numOfEliteChromosomes
     */
    public int getNumOfEliteChromosomes() {
        return numOfEliteChromosomes;
    }

    /**
     * @param numOfEliteChromosomes the numOfEliteChromosomes to set
     */
    public void setNumOfEliteChromosomes(int numOfEliteChromosomes) {
        this.numOfEliteChromosomes = numOfEliteChromosomes;
    }

    /**
     * @return the allowDublicateChromosomes
     */
    public boolean isAllowDublicateChromosomes() {
        return allowDublicateChromosomes;
    }

    /**
     * @param allowDublicateChromosomes the allowDublicateChromosomes to set
     */
    public void setAllowDublicateChromosomes(boolean allowDublicateChromosomes) {
        this.allowDublicateChromosomes = allowDublicateChromosomes;
    }

    /**
     * @return the crossoverOperator
     */
    public ICrossoverOperator<T> getCrossoverOperator() {
        return crossoverOperator;
    }

    /**
     * @param crossoverOperator the crossoverOperator to set
     */
    public void setCrossoverOperator(ICrossoverOperator<T> crossoverOperator) {
        this.crossoverOperator = crossoverOperator;
    }

    /**
     * @return the mutator
     */
    public IMutator<T> getMutator() {
        return mutator;
    }

    /**
     * @param mutator the mutator to set
     */
    public void setMutator(IMutator<T> mutator) {
        this.mutator = mutator;
    }

    /**
     * @return the chromosomeFactory
     */
    public IChromosomeFactory<T> getChromosomeFactory() {
        return chromosomeFactory;
    }

    /**
     * @param chromosomeFactory the chromosomeFactory to set
     */
    public void setChromosomeFactory(IChromosomeFactory<T> chromosomeFactory) {
        this.chromosomeFactory = chromosomeFactory;
    }

    /**
     * @return the selector
     */
    public ISelection<T> getSelector() {
        return selector;
    }

    /**
     * @param selector the selector to set
     */
    public void setSelector(ISelection<T> selector) {
        this.selector = selector;
    }

    /**
     * @return the maxNumOfIterations
     */
    public int getMaxNumOfIterations() {
        return maxNumOfIterations;
    }

    /**
     * @param maxNumOfIterations the maxNumOfIterations to set
     */
    public void setMaxNumOfIterations(int maxNumOfIterations) {
        this.maxNumOfIterations = maxNumOfIterations;
    }

    /**
     * @return the maxNumOfUselessIterations
     */
    public int getMaxNumOfIterationsWOProgress() {
        return maxNumOfIterationsWOProgress;
    }

    /**
     * @param maxNumOfIterationsWOProgress the maxNumOfIterationsWOProgress to set
     */
    public void setMaxNumOfIterationsWOProgress(int maxNumOfIterationsWOProgress) {
        this.maxNumOfIterationsWOProgress = maxNumOfIterationsWOProgress;
    }

    /**
     * @return the populationSize
     */
    public int getPopulationSize() {
        return populationSize;
    }

    /**
     * @param populationSize the populationSize to set
     */
    public void setPopulationSize(int populationSize) {
        this.populationSize = populationSize;
    }

    /**
     * @return the numOfChildren
     */
    public int getNumOfChildren() {
        return numOfChildren;
    }

    /**
     * @param numOfChildren the numOfChildren to set
     */
    public void setNumOfChildren(int numOfChildren) {
        this.numOfChildren = numOfChildren;
    }

    /**
     * @return the mutationRate
     */
    public double getMutationRate() {
        return mutationRate;
    }

    /**
     * @param mutationRate the mutationRate to set
     */
    public void setMutationRate(double mutationRate) throws Exception {
        if ((greedRate > 1.0) || (greedRate < 0.0))
            throw new Exception("It should be between 0.0 and 1.0!");
        this.mutationRate = mutationRate;
    }

    /**
     * @return the geneticIterationListener
     */
    public GeneticIterationListener<T> getGeneticIterationListener() {
        return geneticIterationListener;
    }

    /**
     * @param geneticIterationListener the geneticIterationListener to set
     */
    public void setGeneticIterationListener(GeneticIterationListener<T> geneticIterationListener) {
        this.geneticIterationListener = geneticIterationListener;
    }

    /**
     * @return the greedRate
     */
    public double getGreedRate() {
        return greedRate;
    }

    /**
     * @param greedRate the greedRate to set
     */
    public void setGreedRate(double greedRate) throws Exception {
        if ((greedRate > 1.0) || (greedRate < 0.0))
            throw new Exception("It should be between 0.0 and 1.0!");
        this.greedRate = greedRate;
    }

    /**
     * @return the comparator
     */
    public Comparator<IChromosome<T>> getComparator() {
        return comparator;
    }

    /**
     * @param comparator the comparator to set
     */
    public void setComparator(Comparator<IChromosome<T>> comparator) {
        this.comparator = comparator;
    }

    /**
     * @return the optimizerIndex
     */
    public int getOptimizerIndex() {
        return optimizerIndex;
    }

    /**
     * @param optimizerIndex the optimizerIndex to set
     */
    public void setOptimizerIndex(int optimizerIndex) {
        this.optimizerIndex = optimizerIndex;
    }

}
